﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Forms;
using Noris.Tools.TraceVisualiser.Components;

namespace Noris.Tools.TraceVisualiser.Green.AplThreads
{
	#region EventItem : data jedné události v trace datech klienta, typicky jde o párovou záležitost (řádky Begin + End v jednom záznamu Event).
	/// <summary>
	/// EventItem : data jedné události v trace datech klienta, typicky jde o párovou záležitost (řádky Begin + End v jednom záznamu Event).
    /// </summary>
    public class EventItem : IEventItem, TraceVisualiser.Forms.IMasterDetails
    {
        #region Vytvoření instance - tato třída se nevytváří v TraceData, ale už při načítání
        /// <summary>
        /// Vytvoří a vrátí instanci objektu odpovídajícího danému vstupnímu řádku.
        /// Nicméně do objektu nic datového nevepisuje, to se provádí voláním metody AddData.
        /// Data v parametru "item" slouží jen k určení konkrétního typu vznikající instance (Class Factory).
        /// </summary>
        /// <param name="stringHeap"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        internal static EventItem CreateEventItem(StringHeap stringHeap, TraceItem item)
        {
            EventItem info = new EventItem(stringHeap, item);
            return info;
        }
        private EventItem(StringHeap stringHeap, TraceItem item)
        {
            this.StringHeap = stringHeap;
            this.RowCount = 0;
            this.Time = TimeRange.Empty;
            this._BooleanValues = 0;
            this.Properties = ElementProperties.Selectable;
            this.CaptionShowAllways = true;
            this.LineNumbers = new List<int>();

            this.BackColor = Color.Empty;

            // Už zde musím převzít údaje pro určení ProcessKey a EventKey (z itemu):
			this.UserInt32 = item.UserInt32;
			this.ProfileInt32 = item.ProfileInt32;
			this.ThreadId = item.ThreadId;
			this.CreatedSec = item.CreatedSec;
        }
        protected StringHeap StringHeap;
        #endregion
		#region Úložiště boolean hodnot v jednom Int32
        /// <summary>
        /// Vrátí hodnotu Boolean z daného čísla bitu.
        /// Základní třída EventItem nechť využívá bity 0-15, potomek číslo 1 využívá bity 16 - 23, potomek číslo 2 bity 24 - 30.
        /// </summary>
        /// <param name="bite"></param>
        /// <returns></returns>
        protected bool BooleanGet(int bit)
        {
            return ((_BooleanValues & (1 << bit)) > 0);
        }
        /// <summary>
        /// Uloží hodnotu Boolean do daného čísla bitu.
        /// Základní třída EventItem nechť využívá bity 0-15, potomek číslo 1 využívá bity 16 - 23, potomek číslo 2 bity 24 - 30.
        /// </summary>
        /// <param name="bit"></param>
        /// <param name="value"></param>
        protected void BooleanSet(int bit, bool value)
        {
            int mask = 1 << bit;
            if (value)
                _BooleanValues = _BooleanValues | mask;
            else
                _BooleanValues = _BooleanValues & (mask ^ int.MaxValue);
        }
		/// <summary>
		/// Společné úložiště pro hodnoty Bool (jeden bit tohoto Int32 = jedna Bool hodnota
		/// </summary>
        private int _BooleanValues;
        #endregion
        #region Plnění objektu Event z dat objektu Item
        /// <summary>
        /// Do objektu this vepíše data z příchozí události (řádek z trace souboru)
        /// </summary>
        /// <param name="eventEdge"></param>
        /// <param name="traceInfo"></param>
        protected virtual void AddData(IEventEdge eventEdge, TraceInfo traceInfo)
        {
            TraceItem item = eventEdge as TraceItem;
            if (item == null) return;

            this.AddData(item, traceInfo);
        }
        /// <summary>
        /// Do objektu this vepíše data z příchozí události (řádek z trace souboru)
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
        public void AddData(TraceItem item, TraceInfo traceInfo)
        {
            if (this.RowCount == 0)
                this.AddDataFirst(item, traceInfo);
            else
                this.AddDataNext(item, traceInfo);

            this.AddDataAfter(item, traceInfo);

            this.PrepareTexts(item, traceInfo);
        }
        /// <summary>
        /// Do this vloží vstupující data - první nalezený řádek (pro konkrétní Event)
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
        protected virtual void AddDataFirst(TraceItem item, TraceInfo traceInfo)
        {
            this.Time = new TimeRange(item.Created, item.Time);
            this.ThreadId = item.ThreadId;
            this.ManagedId = item.ManagedId;
            this.UserInt32 = item.UserInt32;
            this.ProfileInt32 = item.ProfileInt32;
            this.Created = item.Created;
            this.LastResume = item.LastResume;
            this.LastSuspend = item.LastSuspend;
            this.Suspended = item.Suspended;
            this.OverallTime = item.OverallTime;
            this.SuspendCount = item.SuspendCount;
            this.TypeInt32 = item.TypeInt32;
            this.ActionInt32 = item.ActionInt32;

            this.DescriptionsLength = item.DescriptionsLength;
            if (item.DescriptionsLength > 0)
            {
                this.DescriptionsInt32 = new int[item.DescriptionsLength];
                item.DescriptionsInt32.CopyTo(this.DescriptionsInt32, 0);
            }
            else
	        {
                this.DescriptionsInt32 = null;
	        }
          
            this.TargetHomeNode = null;
            this.TargetPaths = new string[] { NorisThreadConstants.RootNodeClient + "\\" + this.User };
            this.TreePaths = null;
            this.Trace = traceInfo;
            
            this.Visible = true;

        }
        /// <summary>
        /// Do this vloží vstupující data - každý nalezený řádek (kromě prvního)
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
        protected virtual void AddDataNext(TraceItem item, TraceInfo traceInfo)
        {
            bool isNext = (item.Time > this.Time.End);                              // true = vstupující item je z času pozdějšího než máme dosud nastřádáno

            this.Time = this.Time + new TimeRange(item.Created, item.Time);         // Prodloužím náš čas o čas vstupující (oboustranně)

            if (isNext)
            {
                this.LastResume = item.LastResume;
                this.LastSuspend = item.LastSuspend;
                this.Suspended = item.Suspended;
                this.OverallTime = item.OverallTime;
                this.SuspendCount = item.SuspendCount;
            }
        }
        /// <summary>
        /// Provede úpravy this po vložení dat, po každém itemu (první i následný)
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
        protected virtual void AddDataAfter(TraceItem item, TraceInfo traceInfo)
        {
            // Celková doba threadu ovlivní barevnost:
            // Barvy budou plynule přechodové, což zajistí ColorBlend:
            LinearColorBlender lcb = Steward.GetFromCache(typeof(ThreadColorBlender)) as LinearColorBlender;   // ThreadColorBlender je v systémové cache. Při prvním použití je vytvořen a inicializován (viz definice třídy ThreadColorBlender).
            this.BackColor = lcb.Get((float)this.Time.Seconds, Color.LightGray);

            this.LineNumbers.Add(item.LineNumber);
            this.RowCount++;
        }
        /// <summary>
        /// Z dat v objektu (a v parametrech) připraví texty do elementu a do tooltipu
        /// </summary>
        /// <param name="eventEdge"></param>
        /// <param name="traceInfo"></param>
        protected virtual void PrepareTexts(TraceItem item, TraceInfo traceInfo)
        {
        }
        #endregion
        #region class ThreadColorBlender : míchání barev elementů
        /// <summary>
        /// Třída LinearColorBlender, potomek:
        /// - jednak kvůli exaktnímu použití v Cache (1 instance per Type), 
        /// - a jednak kvůli metodě IInitialize.Initialize()
        /// </summary>
        private class ThreadColorBlender : LinearColorBlender, IInitialize
        {
            #region IInitialize Members

            void IInitialize.Initialize()
            {
                // Čas od (sekund), barva platná pro daný čas: (barvy v mezičasech jsou plně interpolovány, barvy v čase před prvním a po posledním čas jsou rovny barvě v mezní pozici):
                this.Add(0F, Color.PaleGreen);
                this.Add(30F, Color.Orange);
                this.Add(120F, Color.Tomato);
                this.Add(480F, Color.Red);
                this.Add(1800F, Color.DarkViolet);
            }

            #endregion
        }
        #endregion
        #region Tvorba textu Caption a Tooltip
        /// <summary>
        /// Vrátí sadu textů pro Captions. Nadřízená metoda si z nich vybere text vhodný pro aktuální velikost elementu.
        /// </summary>
        /// <returns></returns>
        protected virtual string[] CreateCaptions()
        {
            string[] captions = new string[] {
                this.Action
            };

            return captions;
        }
        /// <summary>
        /// Vrátí ToolTip, vždy když je potřeba.
        /// </summary>
        /// <returns></returns>
        protected virtual string CreateToolTipInfo()
        {
            string eol = Environment.NewLine;
            string toolTip = this.Time.StringFullDetail + eol +
                "ThreadId: " + this.ThreadId.ToString() + eol +
                "ManagedId: " + this.ManagedId.ToString() + eol +
                "User: " + this.User + eol +
                "Profile: " + this.Profile + eol +
                "Created: " + this.Created.ToString("yyyy-MM-dd HH:mm:ss") + eol +
                "Last resume: " + this.LastResume.ToString() + eol +
                "Last suspend: " + this.LastSuspend.ToString() + eol +
                "Suspended: " + this.Suspended.ToString() + eol +
                "Overall time: " + this.OverallTime.ToString() + eol +
                "Suspend Count: " + this.SuspendCount.ToString() + eol +
                "Type: " + this.Type + eol +
                "Action: " + this.Action + eol +
                eol +
                _GetTraceText() + eol +
                eol +
                "Descriptions: Count = " + this.DescriptionsLength.ToString() + eol;

            // Descriptions
            if (this.DescriptionsLength > 0)
            {
                foreach (Int32 d in this.DescriptionsInt32)
                    toolTip += "   " + this.StringHeap.GetString(d) + eol;
            }

            return toolTip;
        }
        private string _GetTraceText()
        {
            if (this.Trace == null) return "TraceFile: ???";
            string lines = _GetLineNumbers();
            return "TraceFile: " + this.Trace.FileDirName + ", Lines: " + (lines.Length == 0 ? "" : lines.Substring(0, lines.Length - 1));
        }
        private string _GetLineNumbers()
        {
            string lines = "";
            int cnt = 0;
            foreach (int l in this.LineNumbers)
            {
                lines += l.ToString() + ",";
                if (cnt == 25 && this.LineNumbers.Count > 26)
                {
                    lines += "....";
                    break;
                }
                cnt++;
            }
            return lines;
        }
        #endregion
        #region Public datové property

		// Data ze souboru
        /// <summary>
        /// Počet řádků načtených do objektu
        /// </summary>
        public int RowCount { get; protected set; }
		/// <summary>
		/// Vyjadřuje čas, po který je thread evidován (od prvopočátku = Created do posledního výskytu v trace)
		/// </summary>
		public TimeRange Time { get; protected set; }

		public int GCMemoryB { get; protected set; }
		public int ThreadId { get; protected set; }
		public int ManagedId { get; protected set; }
        public string User { get { return this.StringHeap.GetString(this.UserInt32); } }
        public Int32 UserInt32 { get; protected set; }
		public string Profile { get { return this.StringHeap.GetString(this.ProfileInt32); } }
        public Int32 ProfileInt32 { get; protected set; }
		public DateTime Created { get; protected set; }
		public int CreatedSec { get; protected set; }
		public DateTime LastResume { get; protected set; }
		public DateTime LastSuspend { get; protected set; }
		public bool Suspended { get; protected set; }
		public int OverallTime { get; protected set; }
		public int SuspendCount { get; protected set; }
		public string Type { get { return this.StringHeap.GetString(TypeInt32); } }
		public Int32 TypeInt32 { get; protected set; }
		public string Action { get { return this.StringHeap.GetString(ActionInt32); } }
		public Int32 ActionInt32 { get; protected set; }
		public int DescriptionsLength { get; protected set; }
		public Int32[] DescriptionsInt32 { get; protected set; }

        // Data pro zobrazování
        public List<int> LineNumbers { get; protected set; }
        public string TargetHomeNode { get; protected set; }
        public string[] TargetPaths { get; protected set; }
        public TreePath[] TreePaths { get; protected set; }
        public string ColorGroup { get; protected set; }
        public int ColorIndex { get; protected set; }
        public bool Visible { get { return BooleanGet(0); } protected set { BooleanSet(0, value); } }
        public bool HideZeroWidthElementRectangle { get { return BooleanGet(5); } protected set { BooleanSet(5, value); } }
        public int ElementGroup { get; protected set; }
        public GraphElementShapeType ElementShapeType { get; protected set; }
        public ElementProperties Properties { get; protected set; }
        public Color BackColor { get; protected set; }
        public bool CaptionShowAllways { get { return BooleanGet(6); } protected set { BooleanSet(6, value); } }
        public Image ElementImage { get; protected set; }
        public TraceInfo Trace { get; protected set; }

        /// <summary>
        /// Vrátí SubTimeSort = číslo trace v čase + číslo řádky
        /// </summary>
        /// <returns></returns>
        private int _GetSubTimeSort()
        {
            // Vrací Int32, který obsahuje bit 31 = 0 (kladné číslo), bity 30 ÷ 25 (6 bitů) = číslo TraceOrder, bity 24 ÷ 0 = číslo LineNumbers[0]:
            return (this.Trace == null ? 0 : (this.Trace.TraceOrder & 0x003F) << 25) | (this.LineNumbers.Count == 0 ? 0 : this.LineNumbers[0] & 0x00FFFFFF);
        }
        #endregion
        #region IEventPointer Members
        string IEventPointer.ProcessKey { get { return this.User + "." + this.Profile; } }
        string IEventPointer.EventKey { get { return this.ThreadId.ToString() + "." + this.CreatedSec.ToString(); } }
        int IEventPointer.LineNumber { get { return (this.LineNumbers.Count == 0 ? 0 : this.LineNumbers[0]); } }
        #endregion
        #region IEventItem Members
        string IEventItem.MergeKey { get { return null; } }
        string IEventItem.TargetHomeNode { get { return this.TargetHomeNode; } }
        string[] IEventItem.TargetPaths { get { return this.TargetPaths; } }
        TreePath[] IEventItem.TreePaths { get { return this.TreePaths; } }
        TimeRange IEventItem.Time { get { return this.Time; } }
        void IEventItem.AddData(IEventEdge eventEdge, TraceInfo traceInfo)
        {
            this.AddData(eventEdge, traceInfo);
        }
        bool IEventItem.MergeWith(IEventItem nextEventItem) { return false; }
        int IEventItem.ColorIndex { get { return this.ColorIndex; } }
        #endregion
        #region IDataElement Members
        bool IDataElement.ElementVisible { get { return this.Visible; } }
        GID IDataElement.GId { get { return GID.Empty; } }                            // Zde není GID uložen, nemohu jej určit, vrátím Empty. On tento objekt de facto není plnohodnotný IDataElement (právě chybějícím GIDem), ale nechce se mi tvořit jiný interface, shodný s IDataElement až na nepřítomnost GIDu...
        TimeRange IDataElement.TimeRange { get { return this.Time; } set { } }
		float IDataElement.ElementHeightRatio { get { return 0f; } }
		bool IDataElement.BeginIsUncertain { get { return false; } }
        bool IDataElement.EndIsUncertain { get { return false; } }
        int IDataElement.SubTimeSort { get { return _GetSubTimeSort(); } }
        bool IDataElement.FullRow { get { return false; } }
        bool IDataElement.HideZeroWidthElementRectangle { get { return HideZeroWidthElementRectangle; } }
        float[] IDataElement.Fragments { get { return null; } }
        GraphLayerType IDataElement.ElementLayer { get { return GraphLayerType.ElementItemLayer; } }
        int IDataElement.ElementGroup { get { return this.ElementGroup; } }
        GraphElementShapeType IDataElement.ElementShapeType { get { return this.ElementShapeType; } }
        ElementProperties IDataElement.Properties { get { return this.Properties; } }
        ElementActionType IDataElement.ActionOnDoubleClick { get { return ElementActionType.DefaultByRow; } }
        GraphElementHandler IDataElement.DoubleClick { get { return null; } }
        string IDataElement.ColorGroup { get { return this.ColorGroup; } }
		Color IDataElement.BackColor { get { return this.BackColor; } set { this.BackColor = value; } }
        Color IDataElement.FontColor { get { return Color.Empty; } }
        bool IDataElement.CaptionShowAllways { get { return this.CaptionShowAllways; } }
        Image IDataElement.ElementImage { get { return this.ElementImage; } }
        ContentAlignment IDataElement.ImagePivotElementPoint { get { return ContentAlignment.MiddleCenter; } }
        ContentAlignment IDataElement.ImagePivotImagePoint { get { return ContentAlignment.MiddleCenter; } }
        string[] IDataElement.Captions { get { return CreateCaptions(); } }
        string IDataElement.ToolTipInfo { get { return CreateToolTipInfo(); } }
        string IDataElement.GetToolTipInfo(IDataRow rowData) { return null; }
        string IDataElement.GetCaption(IDataRow rowData, float charCount) { return null; }
        void IDataElement.DrawElement(GraphElementPainterArgs args) { }
        #endregion
        #region IDisposable Members
        public void Dispose()
        {
            this.StringHeap = null;
        }
        #endregion
        #region IMasterDetails Members
        string IMasterDetails.MasterName
        {
            get
            {
                return this.Time.Begin + "; " + this.User + ": " + this.Action;
            }
        }
        Color IMasterDetails.DetailBackColor { get { return this.BackColor; } }
        public IEnumerable<MasterDetailItem> Details { get { return this.CreateDetails(); } }
        /// <summary>
        /// Vytvoří, naplní a vrátí seznam informací Details.
        /// postupně volá metody DetailFillTraceTime(); DetailFill(); DetailFillUserData();
        /// </summary>
        /// <returns></returns>
        protected virtual IEnumerable<MasterDetailItem> CreateDetails()
        {
            List<MasterDetailItem> result = new List<MasterDetailItem>();

            this.DetailFillTraceTime(result);
            this.DetailFillItems(result);
            this.DetailFillUserData(result);

            return result;
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence.
        /// Třída EventItem vkládá tato data: "Trace, LineNumber"
        /// </summary>
        /// <param name="result"></param>
        protected virtual void DetailFillTraceTime(List<MasterDetailItem> result)
        {
            // Trace, lines, time:
            this.DetailFillTraceInfo("", this.Trace, null, result);
            result.Add(new MasterDetailItem("Lines", this._GetLineNumbers()));
            result.Add(new MasterDetailItem("Time", this.Time.StringFullDetail));
        }
        /// <summary>
        /// Do sady řádků (detaily) vloží data o daném trace.
        /// </summary>
        /// <param name="title">SubTitulek: například "", ".Begin", ".End" atd</param>
        /// <param name="traceInfo">Objekt TraceInfo. Může být null.</param>
        /// <param name="lineNumber">Číslo řádku. Pokud bude null, nebude se vypisovat.</param>
        /// <param name="result">Seznam řádků, kam se detaily vkládají.</param>
        protected void DetailFillTraceInfo(string title, TraceInfo traceInfo, Int32? lineNumber, List<MasterDetailItem> result)
        {
            if (traceInfo == null)
            {
                result.Add(new MasterDetailItem("Trace" + title, "???"));
            }
            else
            {
                result.Add(new MasterDetailItem("Trace" + title, traceInfo.FullFileName));
                if (lineNumber.HasValue)
                    result.Add(new MasterDetailItem("Trace" + title + ".Line", lineNumber.Value.ToString()));
            }
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence.
        /// Třída EventItem vkládá tato data: "SecureId, BEDepth, StatusInfo, Login, Stack, ThreadId, RoundtripId, PageId, Type, Method, Keyword"
        /// </summary>
        /// <param name="result"></param>
        protected virtual void DetailFillItems(List<MasterDetailItem> result)
        {
            result.Add(new MasterDetailItem("ThreadId", this.ThreadId));
            result.Add(new MasterDetailItem("ManagedId", this.ManagedId));
            result.Add(new MasterDetailItem("User", this.User));
            result.Add(new MasterDetailItem("Profile", this.Profile));
            result.Add(new MasterDetailItem("Created", this.Created.ToString("yyyy-MM-dd HH:mm:ss")));
            result.Add(new MasterDetailItem("Last resume", this.LastResume.ToString("yyyy-MM-dd HH:mm:ss")));
            result.Add(new MasterDetailItem("Last suspend", this.LastSuspend.ToString("yyyy-MM-dd HH:mm:ss")));
            result.Add(new MasterDetailItem("Suspended", this.Suspended.ToString()));
            result.Add(new MasterDetailItem("Overall time", this.OverallTime));
            result.Add(new MasterDetailItem("Suspend Count", this.SuspendCount));
            result.Add(new MasterDetailItem("Type", this.Type));
            result.Add(new MasterDetailItem("Action", this.Action));
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence, hodnoty pouze z polí UserData[xxx].
        /// </summary>
        /// <param name="result"></param>
        protected virtual void DetailFillUserData(List<MasterDetailItem> result)
        {
            // Descriptions
            if (this.DescriptionsLength > 0)
            {
                for (int p = 0; p < this.DescriptionsLength; p++)
                    result.Add(new MasterDetailItem("Description[" + p.ToString() + "]", this.StringHeap.GetString(this.DescriptionsInt32[p])));
            }
        }
        #endregion
    }
    #endregion
}
